package net.luohuasheng.bee.proxy.mybatis.executor.intercetor;


import net.luohuasheng.bee.jdbc.tools.common.utils.execute.ParameterUtils;
import net.luohuasheng.bee.proxy.mybatis.common.utils.ClassUtils;
import net.luohuasheng.bee.proxy.mybatis.common.utils.MapperUtils;
import net.luohuasheng.bee.proxy.mybatis.executor.criteria.Criteria;
import net.luohuasheng.bee.proxy.mybatis.executor.processor.BaseProcessor;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import static net.luohuasheng.bee.proxy.mybatis.executor.intercetor.BaseInterceptor.Index.MAPPED_STATEMENT_INDEX;
import static net.luohuasheng.bee.proxy.mybatis.executor.intercetor.BaseInterceptor.Index.PARAMETER_INDEX;


/**
 * @author luohuasheng
 */
@Intercepts({
        @Signature(
                type = Executor.class,
                method = "query",
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(
                type = Executor.class,
                method = "query",
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}
        ),
        @Signature(
                type = Executor.class,
                method = "update",
                args = {MappedStatement.class, Object.class}),})
public class WhereInterceptor implements BaseInterceptor {
    public static final String WHERE = "where";
    private static final String AND = "and";

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[MAPPED_STATEMENT_INDEX.getAnInt()];
        Object parameter = args[PARAMETER_INDEX.getAnInt()];
        Criteria criteria = ClassUtils.findObjectFromParams(parameter, Criteria.class);
        if (criteria != null && criteria.size() != 0) {
            Map<String, Object> data = new HashMap<>(0);
            data.putAll(criteria);
            criteria.put("criteria", data);
            BoundSql boundSql = ms.getBoundSql(parameter);
            String sql = addCriteria(boundSql.getSql(), criteria.getSqlSegment());
            if (!criteria.getFields().isEmpty()) {
                sql = ParameterUtils.packageFieldSql(sql, criteria.getFields());
            }
            SqlSource sqlSource = LANGUAGE_DRIVER.createSqlSource(ms.getConfiguration(), "<script>\n\t" + sql + "</script>", null);
            BoundSql newBoundSql = sqlSource.getBoundSql(parameter);
            newBoundSql.getParameterMappings().addAll(0, boundSql.getParameterMappings());
            args[MAPPED_STATEMENT_INDEX.getAnInt()] = MapperUtils.copyFromNewSql(ms, newBoundSql);
        }
        return invocation.proceed();
    }

    private String addCriteria(String sql, String sqlSegment) {
        if (sql.lastIndexOf(WHERE) >= 0) {
            String where = sql.substring(sql.lastIndexOf(WHERE) + 5).trim();
            int left = getCount(where, '(');
            int right = getCount(where, ')');
            if (left == right) {
                sql += " " + AND + " " + sqlSegment;
            } else {
                sql += " " + WHERE + " " + sqlSegment;
            }
        } else {
            sql += " " + WHERE + " " + sqlSegment;
        }
        return sql;
    }

    private int getCount(String str, char key) {
        int count = 0;
        for (char c : str.toCharArray()) {
            if (c == key) {
                count++;
            }
        }
        return count;
    }


    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 合并属性
    }
}
